﻿using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using Notoric.Commanding.CommandBase;

namespace Notoric.ViewModel.Elements.CWNotation
{
    /// <summary>
    /// A common base class for <see cref="Model.Elements.CWNotation.Connector"/> adapters.
    /// </summary>
    public abstract class Connector : ViewModelElement
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Connector"/> class.
        /// </summary>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="System"/>
        /// </para>
        /// <para>
        /// The system that directly owns this connector.
        /// </para>
        /// </param>
        /// <param name="adaptedElement">
        /// <para>
        /// Type: <see cref="Connector"/>
        /// </para>
        /// <para>
        /// The adapted connector element.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="adaptedElement"/> is <see langword="null"/>
        /// </exception>
        /// <exception cref="NullReferenceException">
        /// The <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        protected Connector(System owner, Model.Elements.CWNotation.Connector adaptedElement)
            : base(adaptedElement, owner, owner.UndoStack, owner.RedoStack)
        {
            if (adaptedElement == null)
                throw new ArgumentNullException("adaptedElement");
        }

        #endregion

        #region Event Handlers

        /// <inheritdoc/>
        protected override bool AdaptedElement_PropertyChanged(PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Start")
            {
                var modelStaff = ((Model.Elements.CWNotation.Connector)AdaptedElement).FindOwner<Model.Elements.CWNotation.Staff>();
                var myStaff = FindOwner<System>().Staves.First(s => s.AdaptedElement == modelStaff);
                start = myStaff.Symbols.First(s => s.AdaptedElement == ((Model.Elements.CWNotation.Connector)AdaptedElement).Start) as DurationalSymbol;
            }
            else if (e.PropertyName == "End")
            {
                var modelStaff = ((Model.Elements.CWNotation.Connector)AdaptedElement).FindOwner<Model.Elements.CWNotation.Staff>();
                var myStaff = FindOwner<System>().Staves.First(s => s.AdaptedElement == modelStaff);
                end = myStaff.Symbols.First(s => s.AdaptedElement == ((Model.Elements.CWNotation.Connector)AdaptedElement).End) as DurationalSymbol;
            }

            return base.AdaptedElement_PropertyChanged(e);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates the adapter for a given connector element.
        /// </summary>
        /// <param name="adaptedElement">
        /// <para>
        /// Type: <see cref="Connector"/>
        /// </para>
        /// <para>
        /// The adapted element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="System"/>
        /// </para>
        /// <para>
        /// The system that will directly own the created connector.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Connector"/>
        /// </para>
        /// <para>
        /// The created adapter.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="adaptedElement"/> or <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The type of the <paramref name="adaptedElement"/> is not known.
        /// </exception>
        public static Connector CreateAdapter(Model.Elements.CWNotation.Connector adaptedElement, System owner)
        {
            if (adaptedElement == null)
                throw new ArgumentNullException("adaptedElement");
            if (owner == null)
                throw new ArgumentNullException("owner");

            if (adaptedElement is Model.Elements.CWNotation.Slur)
                return new Slur(owner, adaptedElement as Model.Elements.CWNotation.Slur);
            else if (adaptedElement is Model.Elements.CWNotation.Tie)
                return new Tie(owner, adaptedElement as Model.Elements.CWNotation.Tie);

            throw new ArgumentException("Unknown element type.", "adaptedElement");
        }

        /// <summary>
        /// Creates an undoable command for setting the <see cref="EndingPoint"/> property.
        /// </summary>
        /// <param name="point">
        /// <para>
        /// Type: <see cref="Point"/>
        /// </para>
        /// <para>
        /// The point.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetEndingPointCommand(Point point)
        {
            var pt = new Point(point.X - FindOwner<System>().Left, point.Y - FindOwner<System>().Top);
            var modelPoint = (Model.Elements.Point)StructuresConverter.ConvertBack(pt, typeof(Model.Elements.Point), null, CultureInfo.CurrentCulture);

            return new SetterCommand<Model.Elements.Point>(this, "EndingPoint", modelPoint, UndoStack, RedoStack)
            {
                Name = "Change Position"
            };
        }

        /// <summary>
        /// Creates an undoable command for setting the <see cref="StartingPoint"/> property.
        /// </summary>
        /// <param name="point">
        /// <para>
        /// Type: <see cref="Point"/>
        /// </para>
        /// <para>
        /// The point.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetStartingPointCommand(Point point)
        {
            var pt = new Point(point.X - FindOwner<System>().Left, point.Y - FindOwner<System>().Top);
            var modelPoint = (Model.Elements.Point)StructuresConverter.ConvertBack(pt, typeof(Model.Elements.Point), null, CultureInfo.CurrentCulture);

            return new SetterCommand<Model.Elements.Point>(this, "StartingPoint", modelPoint, UndoStack, RedoStack)
            {
                Name = "Change Position"
            };
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the durational symbol at which the connector ends.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="DurationalSymbol"/>
        /// </para>
        /// <para>
        /// The durational symbol at which the connector ends.
        /// </para>
        /// </value>
        public DurationalSymbol End
        {
            get { return end; }
        }

        /// <summary>
        /// Gets or sets the coordinates of the ending point of the connector.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Point"/>
        /// </para>
        /// <para>
        /// The coordinates (in device independent pixels) of the connector's
        /// ending point relative to the top-left corner of the owner section.
        /// </para>
        /// </value>
        public Point EndingPoint
        {
            get
            {
                var basePoint = (Point)StructuresConverter.Convert(GetPropertyValue<Model.Elements.Point>("EndingPoint"), typeof(Point), null, CultureInfo.CurrentCulture);

                return new Point(basePoint.X + FindOwner<System>().Left, basePoint.Y + FindOwner<System>().Top);
            }
            set
            {
                if (UnsavedChanges.ContainsKey("EndingPoint"))
                {
                    var pt = new Point(value.X - FindOwner<System>().Left, value.Y - FindOwner<System>().Top);
                    var cmd = (SetterCommand<Model.Elements.Point>)UnsavedChanges["EndingPoint"];
                    cmd.Value = (Model.Elements.Point)StructuresConverter.ConvertBack(pt, typeof(Model.Elements.Point), null, CultureInfo.CurrentCulture);
                }
                else
                    UnsavedChanges["EndingPoint"] = CreateSetEndingPointCommand(value);

                RaisePropertyChanged("EndingPoint");
            }
        }

        /// <summary>
        /// Gets the durational symbol at which the connector starts.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="DurationalSymbol"/>
        /// </para>
        /// <para>
        /// The durational symbol at which the connector starts.
        /// </para>
        /// </value>
        public DurationalSymbol Start
        {
            get { return start; }
        }

        /// <summary>
        /// Gets or sets the coordinates of the starting point of the connector.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Point"/>
        /// </para>
        /// <para>
        /// The coordinates (in device independent pixels) of the connector's
        /// starting point relative to the top-left corner of the owner section.
        /// </para>
        /// </value>
        public Point StartingPoint
        {
            get 
            { 
                var basePoint = (Point)StructuresConverter.Convert(GetPropertyValue<Model.Elements.Point>("StartingPoint"), typeof(Point), null, CultureInfo.CurrentCulture);

                return new Point(basePoint.X + FindOwner<System>().Left, basePoint.Y + FindOwner<System>().Top);
            }
            set
            {
                if (UnsavedChanges.ContainsKey("StartingPoint"))
                {
                    var pt = new Point(value.X - FindOwner<System>().Left, value.Y - FindOwner<System>().Top);
                    var cmd = (SetterCommand<Model.Elements.Point>)UnsavedChanges["StartingPoint"];
                    cmd.Value = (Model.Elements.Point)StructuresConverter.ConvertBack(pt, typeof(Model.Elements.Point), null, CultureInfo.CurrentCulture);
                }
                else
                    UnsavedChanges["StartingPoint"] = CreateSetStartingPointCommand(value);

                RaisePropertyChanged("StartingPoint");
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="End"/> property.
        /// </summary>
        private DurationalSymbol end;
        /// <summary>
        /// Holds the value of the <see cref="Start"/> property.
        /// </summary>
        private DurationalSymbol start;

        #endregion
    }
}
